Skip to content

Require tuple before env in the prelude (#8771)#17088

Closed
stakach wants to merge 1 commit into
crystal-lang:masterfrom
stakach:fix-tuple-enumerable-upcast
Closed

Require tuple before env in the prelude (#8771)#17088
stakach wants to merge 1 commit into
crystal-lang:masterfrom
stakach:fix-tuple-enumerable-upcast

Conversation

@stakach

@stakach stakach commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Resolves #8771 (and its duplicate #17086).

Issue

{"a", "b"}.as Enumerable(String) crashes the compiler:

Element not found (Enumerable::NotFoundError)
  ... in 'upcast_distinct' (src/compiler/crystal/codegen/cast.cr)

It only happens when a tuple has 2+ elements whose splat-union collapses to a single type (Union(String, String) == String). {"a"}, {"a", 1}, {"a", "b", "c"} all work.

Root cause

Require ordering. Tuple does include Indexable(Union(*T)) (which transitively includes Enumerable/Iterable), but tuple was required after env. ENV does extend Enumerable({String, String}), which instantiates Tuple(String, String) before Tuple gained those module parents. Its after_initialize therefore ran with an incomplete parent list (only Value) and never registered the instance as an including type of Enumerable(String). It was consequently missing from Enumerable(String).including_types — the union codegen materializes for as Enumerable(String) — so upcast_distinct couldn't find it and raised. Other arities worked because they're instantiated after tuple.cr is loaded.

Fix

Require tuple (and named_tuple) in the prelude's ordered block, right after indexable, so the tuple includes are processed before any file that can trigger an early tuple instantiation (such as env). All arities then register correctly. No compiler-side change is needed.

A codegen spec covers the original reproducer.

`{"a", "b"}.as Enumerable(String)` crashed codegen with
`Enumerable::NotFoundError`: `Tuple(String, String)` was missing from
`Enumerable(String)`'s including types.

The cause is require ordering. `Tuple` does `include Indexable(Union(*T))`
(which transitively includes `Enumerable`/`Iterable`), but `tuple` was
required after `env`. `ENV` does `extend Enumerable({String, String})`, which
instantiates `Tuple(String, String)` before `Tuple` gained those module
parents, so its `after_initialize` registered nothing and it never appeared in
`Enumerable(String).including_types`. Codegen then failed to find it when
upcasting. Other arities worked because they were instantiated later.

Requiring `tuple` (and `named_tuple`) in the ordered block right after
`indexable` ensures the includes are processed before any early
instantiation, so all arities register correctly. This replaces the need for
any compiler-side workaround.

A codegen spec covers the original reproducer.
@stakach stakach force-pushed the fix-tuple-enumerable-upcast branch from 1a665f0 to a57b0f1 Compare June 20, 2026 23:10
@stakach stakach changed the title Fix codegen crash upcasting a tuple to a collapsed-union Enumerable (#8771) Require tuple before env in the prelude (#8771) Jun 20, 2026
@stakach

stakach commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

#17094 is a better solution

@stakach stakach closed this Jun 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

{"a", "b"}.as Enumerable(String) crashes the compiler

1 participant